package movee.spring.cloud.gateway.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.util.CollectionUtils;
import org.springframework.web.reactive.result.view.ViewResolver;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 创建sentinel相关组件
 *
 * @author 54740559@qq.com
 */
@Configuration
@Slf4j
public class SentinelConfig {

    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;
    private final SentinelProperties sentinelProperties;

    public SentinelConfig(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                         ServerCodecConfigurer serverCodecConfigurer,
                          SentinelProperties sentinelProperties) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
        this.sentinelProperties = sentinelProperties;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        // Register the block exception handler for Spring Cloud Gateway.
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    @Bean
    @Order(-1)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    @PostConstruct
    public void doInit() {
        initCustomizedApis();
        initGatewayRules();
    }

    /**
     * 基于分组配置流控和熔断规则
     */
    private void initCustomizedApis() {
        // empty
    }

    /**
     * 基于route配置流控和熔断规则
     */
    private void initGatewayRules() {
        initGatewayFlowRules();
        initGatewayDegradeRules();
    }

    private void initGatewayFlowRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        if (!CollectionUtils.isEmpty(sentinelProperties.getFlowRules())) {
            for (SentinelProperties.FlowRuleConfig flowRuleConfig : sentinelProperties.getFlowRules()) {
                if (!CollectionUtils.isEmpty(flowRuleConfig.getResources())) {
                    for (String resource : flowRuleConfig.getResources()) {
                        rules.add(new GatewayFlowRule(resource)
                                .setCount(flowRuleConfig.getCount())
                                .setIntervalSec(flowRuleConfig.getIntervalSec())
                        );
                    }
                }
            }
        }
        GatewayRuleManager.loadRules(rules);
    }

    private void initGatewayDegradeRules() {
        List<DegradeRule> rules = new ArrayList<>();
        if (!CollectionUtils.isEmpty(sentinelProperties.getDegradeRules())) {
            for (SentinelProperties.DegradeRuleConfig degradeRuleConfig : sentinelProperties.getDegradeRules()) {
                if (!CollectionUtils.isEmpty(degradeRuleConfig.getResources())) {
                    for (String resource : degradeRuleConfig.getResources()) {
                        rules.add(new DegradeRule(resource)
                                .setGrade(degradeRuleConfig.getGrade().getType())
                                // Max allowed response time
                                .setCount(degradeRuleConfig.getCount())
                                // Retry timeout (in second)
                                .setTimeWindow(degradeRuleConfig.getTimeWindow())
                                // Circuit breaker opens when slow request ratio > 60%
                                // .setSlowRatioThreshold(degradeRuleConfig.getSlowRatioThreshold())
                                .setMinRequestAmount(degradeRuleConfig.getMinRequestAmount())
                                .setStatIntervalMs(degradeRuleConfig.getStatIntervalMs()));
                    }
                }
            }
        }
        DegradeRuleManager.loadRules(rules);
    }

}
